package com.sunshine.security;

import com.sunshine.bean.User;
import com.sunshine.constant.UserRole;
import com.sunshine.exception.HashRegisteredUserException;
import com.sunshine.exception.InvalidUserException;
import com.sunshine.exception.PasswordIncorrectException;
import com.sunshine.model.UserMapper;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Optional;

/**
 * Created by along on 4/19/18.
 */
@Service
@Primary
public class JwtAuthService implements AuthService {

    private static final Logger LOGGER = ESLoggerFactory.getLogger(JwtAuthService.class);

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private UserMapper userMapper;


    /**
     * 确保注册的时候数据库中不存在有效账户
     *
     * @param user
     * @throws InvalidUserException
     * @throws HashRegisteredUserException
     */
    private void check(User user) throws InvalidUserException, HashRegisteredUserException {
        if (user.getRole() == null || user.getName() == null || user.getPassword() == null || user.getNickname() == null) {
            throw new InvalidUserException("User role/name/password/nickname must not be null");
        }

        User userFind = userMapper.findByName(user.getName());

        if (userFind != null && !userFind.getRole().equals(UserRole.DELETED_USER)) {
            throw new HashRegisteredUserException("username " + user.getName() + "has registered");
        }
    }

    private void checkPassword(User user, UserDetails userDetails) throws PasswordIncorrectException {
        if (!user.getPassword().equals(userDetails.getPassword())) {
            throw new PasswordIncorrectException();
        }
    }

    private User createUser(User user) {
        LOGGER.info("creating account : " + user.toString());
        userMapper.insert(user);

        return user;
    }

    private String encryptPassword(String password) {
        return DigestUtils.sha1Hex(password);
    }

    private Optional<JwtUser> innerLogin(User user) throws UsernameNotFoundException {
        JwtUser jwtUser = (JwtUser) userDetailsService.loadUserByUsername(user.getName());
        jwtUser.setCurrentToken(JsonWebToken.generateToken(jwtUser.getId()));

        return Optional.of(jwtUser);
    }

    @Override
    public Optional<JwtUser> register(User user) throws InvalidUserException, HashRegisteredUserException {

        check(user);

        user.setPassword(encryptPassword(user.getPassword()));

        return Optional.ofNullable(userMapper.findByName(user.getName()))
                .map(userFound -> {
                    userFound.setRole(user.getRole());
                    userFound.setNickname(user.getNickname());
                    userFound.setPassword(user.getPassword());

                    LOGGER.info("reactive deleted user :" + user.toString());
                    userMapper.update(userFound);

                    return innerLogin(userFound);
                })
                .orElse(innerLogin(createUser(user)));
    }

    /**
     * @param user
     * @return
     * @throws PasswordIncorrectException
     */
    @Override
    public Optional<JwtUser> login(User user) throws PasswordIncorrectException {
        user.setPassword(encryptPassword(user.getPassword()));
        Optional<JwtUser> userdetails = innerLogin(user);

        checkPassword(user, userdetails.get());

        LOGGER.info("user login successful : " + user.getName());
        return userdetails;
    }

    /**
     * 调用者需要验证权限,调用前用老密码成功登录一次或者管理员
     *
     * @param user
     * @param newPassword
     * @return
     */
    public Optional<JwtUser> changePassword(User user, String newPassword) {
        user.setPassword(encryptPassword(newPassword));

        LOGGER.info("updating account : " + user.toString());
        userMapper.update(user);

        return innerLogin(user);
    }
}
